package main.java.LeetCode.ClassicalAlgorithm.Queue.Impl;

import main.java.LeetCode.ClassicalAlgorithm.Queue.Queue;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class LinkedBlockQueue<E> implements Queue<E> {
    private  Node<E> head = new Node<>();
    private  Node<E> tail = head;
    AtomicInteger count = new AtomicInteger(0);

    private final ReentrantLock putLock = new ReentrantLock();
    private final ReentrantLock takeLock = new ReentrantLock();

    private final Condition isEmpty = takeLock.newCondition();
    private final Condition isFull = putLock.newCondition();

    private final int capacity;

    public LinkedBlockQueue(int capacity){
        this.capacity = capacity;
    }
    @Override
    public E get() throws InterruptedException {
        E x;
        int c = -1;

        takeLock.lockInterruptibly();
        try {
            while (count.get()==0){
                isEmpty.await();
            }
            x = dequeue();

            c = count.getAndDecrement();

            if(c>1)
                isEmpty.signal();
        }finally {
            takeLock.unlock();
        }

        if(c==capacity)
            signalIsFull();
        return x;
    }

    @Override
    public boolean set(E e) throws InterruptedException {
        putLock.lockInterruptibly();
        int c = -1;
        try {
            while (count.get()==capacity){
                isFull.await();
            }

            enqueue(e);
            c = count.getAndIncrement();

            if(c<capacity-1)
                isFull.signal();
        }finally {
            putLock.unlock();
        }

        if(c==0)
            signalIsEmpty();
        return true;
    }

    @Override
    public E get(long timeout, TimeUnit unit) throws InterruptedException {
        E x = null;
        int c = -1;
        long nanos = unit.toNanos(timeout);

        takeLock.lockInterruptibly();
        try {
            while (count.get()==0){
                if(timeout<0)
                    return x;
                timeout = isEmpty.awaitNanos(nanos);
            }

            x = dequeue();

            c = count.getAndDecrement();

            if(c>1)
                isEmpty.signal();
        }finally {
            takeLock.unlock();
        }

        if(c==capacity)
            signalIsFull();
        return x;
    }

    @Override
    public boolean offer(E e)  {
//        if(count.get()==capacity)
//            return false;

        int c = -1;
        putLock.lock();
        try {
            if(count.get()<capacity){
                enqueue(e);

                c = count.getAndIncrement();
                if(c<capacity-1)
                    isFull.signal();
            }
        }finally {
            putLock.unlock();
        }

        if(c==0)
            signalIsEmpty();

        return c>=0;
    }

    public E take() throws InterruptedException {
//        if(count.get()<=0)
//            return null;
        E e = null;
        int c = -1;

        takeLock.lockInterruptibly();

        try {
            if(count.get()>0) {
                e = dequeue();
                c = count.getAndDecrement();
                if(c>1)
                    isEmpty.signal();
            }

        }finally {
            takeLock.unlock();
        }
        if(c==capacity)
            signalIsFull();
        return e;

    }

    @Override
    public int size() {
        return this.capacity;
    }


    static class Node<E>{
        E element;
        Node<E> next;

        private Node(E e){
            this.element = e;
        }

        private Node(){}
    }

    private E dequeue(){
        Node<E> first = head.next;
        E element = first.element;
        head.next = null;
        head = first;
        return element;
    }

    private void enqueue(E x){
        tail.next = new Node<E>(x);
        tail = tail.next;
    }

    private void signalIsFull(){
        putLock.lock();
        try {
            isFull.signal();
        }finally {
            putLock.unlock();
        }
    }

    private void signalIsEmpty(){
        takeLock.lock();
        try {
            isEmpty.signal();
        }finally {
            takeLock.unlock();
        }
    }
}
